﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Konves.Scripture
{
    internal class ReferenceSet
    {
        public Reference Reference { get; set; }
        public int StartIndex { get; set; }
        public int EndIndex { get; set; }
    }

    public class Reference : IEnumerable<Verse>, IDisposable
    {
        private IEnumerable<ReferenceSet> _referenceSets;
        private Data.Repository _repository;
        private IEnumerable<VerseSummary> _verseSummaries;

        private RangeCollection _rangeCollection;


        private Data.Repository Repository
        {
            get
            {
                return _repository ?? (_repository = new Data.Repository());
            }
        }

        public static Reference Parse(string referenceString)
        {
            Reference reference;

            if (TryParse(referenceString, out reference))
                return reference;
            else
                throw new FormatException("Reference string is not in the correct format.");
        }

        public static bool TryParse(string referenceString, out Reference reference)
        {
            reference = null;

            RangeCollection rangeCollection;

            if (!RangeCollection.TryParse(referenceString, out rangeCollection))
                return false;
            
            reference = new Reference();
            reference._rangeCollection = rangeCollection;

            if (reference._rangeCollection.Count == 1)
            {
                Range range = reference._rangeCollection.First();

                

                reference.init(range);
            }
            else
            {
                var references = (
                    from range in reference._rangeCollection
                    select new Reference(range)
                    ).ToArray();

                reference._referenceSets = GetReferenceSets(references);

                reference.Length = references.Sum(vc => vc.Length);
            }

            return true;
        }

        #region Costructors

        /// <summary>
        /// Prevents a default instance of the <see cref="Reference"/> class from being created.
        /// </summary>
        /// <param name="range">The range.</param>
        private Reference(Range range)
        {
            init(range);
        }

        private Reference() { }
        #endregion

        private void init(Range range)
        {
            _rangeCollection = new RangeCollection() { range };

            var start = range.Start;
            var end = range.End;

            var limits = this.Repository.GetChapterLimits(start.BookId, start.ChapterNumber, end.BookId, end.ChapterNumber);

            _verseSummaries = GetVerseSummaries(limits, start.VerseNumber);
            this.Length = _verseSummaries.Last().Offset + range.End.VerseNumber;

            string[] asdf = new string[] { };
        }
        
        private static IEnumerable<ReferenceSet> GetReferenceSets(Reference[] references)
        {
            for (int i = 0; i < references.Length; i++)
            {
                yield return new ReferenceSet
                {
                    Reference = references[i],
                    StartIndex = references.Take(i).Sum(vc => vc.Length),
                    EndIndex = references.Take(i).Sum(vc => vc.Length) + references[i].Length - 1
                };
            }
        }
        
        private static IEnumerable<VerseSummary> GetVerseSummaries(IEnumerable<Version.ChapterLimits> chapterLimits, int startVerse)
        {
            return
                from limit in chapterLimits
                select new VerseSummary
                {
                    Book = limit.BookNumber,
                    Chapter = limit.ChapterNumber,
                    Verses = limit.EndVerseNumber,
                    Offset = (
                        from l in chapterLimits
                        where
                            l.BookNumber < limit.BookNumber
                            || (l.BookNumber == limit.BookNumber && l.ChapterNumber < limit.ChapterNumber)

                        select l.EndVerseNumber).Sum() - startVerse + 1
                };
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return _rangeCollection.ToString();
        }

        /// <summary>
        /// Gets a 32-bit integer that represents the total number of
        /// verses in the <see cref="Konves.Scripture.Reference"/>.
        /// </summary>
        public int Length { get; private set; }

        /// <summary>
        /// Gets the <see cref="Konves.Scripture.Verse"/> at the specified index.
        /// </summary>
        public Verse this[int index]
        {
            get
            {
                if (_verseSummaries != null) // single collection
                {
                    var summary = (
                        from vs in _verseSummaries
                        where vs.Offset <= index
                        select vs
                        ).LastOrDefault();

                    int book = summary.Book;
                    int chapter = summary.Chapter;
                    int verse = index - summary.Offset + 1;

                    string bookName = _repository.GetBookInfo(book).Name;

                    Verse v = Verse.Create(bookName, chapter, verse, null, VersePosition.First, RepositoryMode.Safe);

                    if (v == null)
                    {
                        int asdf = 234;
                    }

                    return v;
                }
                else // multi-collection
                {
                    var verseCollectionSet = _referenceSets.Single(vc => index.Between(vc.StartIndex, vc.EndIndex));

                    return verseCollectionSet.Reference[index - verseCollectionSet.StartIndex];
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance has sub-references.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has sub-references; otherwise, <c>false</c>.
        /// </value>
        public bool HasSubReferences
        {
            get
            {
                return _verseSummaries == null;
            }
        }

        /// <summary>
        /// Gets the sub-references.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Reference> GetSubReferences()
        {
            if (_referenceSets == null)
                return new Reference[] { this };
            else
                return _referenceSets.Select(vcs => vcs.Reference);
        }


        #region IEnumerable<Verse>
        private class VerseEnumerator : IEnumerator<Verse>
        {
            private const int newIndex = -1;

            public VerseEnumerator(Reference verseCollection)
            {
                _index = newIndex;
                _verseCollection = verseCollection;
            }

            private int _index;
            private Reference _verseCollection;

            public bool MoveNext()
            {
                _index++;
                return _index < _verseCollection.Length;
            }

            public void Reset()
            {
                _index = newIndex;
            }

            Verse IEnumerator<Verse>.Current
            {
                get { return _verseCollection[_index]; }
            }

            public void Dispose()
            {
                _verseCollection = null;
            }

            object IEnumerator.Current
            {
                get { return _verseCollection[_index]; }
            }
        }

        public IEnumerator<Verse> GetEnumerator()
        {
            var x = new VerseEnumerator(this);

            return x;
        } 
        #endregion

        #region IEnumerable
        IEnumerator IEnumerable.GetEnumerator()
        {
            return new VerseEnumerator(this);
        } 
        #endregion

        #region IDisposable
        public void Dispose()
        {
            _repository = null;
            _verseSummaries = null;
            _referenceSets = null;
        } 
        #endregion
    }

    public class VerseSummary
    {
        public int Book { get; set; }
        public int Chapter { get; set; }
        public int Offset { get; set; }
        public int Verses { get; set; }
    }
}
